#include "tlv.h"
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>

int tlv_send(tlv_t *tlv,int sockfd,uint8_t type,uint32_t length,const uint8_t *value){
    if(length>TLV_VALUE_MAX_SIZE){
        tlv->error_type=TLV_E_VALUE_TO_LARGE;
        return -1;
    }
    
    uint32_t length_net_byte=htonl(length);
    memcpy(tlv->buffer,&type,sizeof(type));
    memcpy(tlv->buffer+sizeof(type),&length_net_byte,sizeof(length));
    memcpy(tlv->buffer+sizeof(type)+sizeof(length),value,length);

    ssize_t sent = send(sockfd,tlv->buffer,sizeof(type)+sizeof(length)+length,MSG_NOSIGNAL);
    if(sent==-1){tlv->error_type=TLV_E_SEND_FAIL;return -1;}
    if(sent==0){tlv->error_type=TLV_E_CONNECT_IS_OVER;return -1;}

    return 0;
}

int tlv_recv(tlv_t *tlv,int sockfd,uint8_t *type,uint32_t *length){
    ssize_t recved = recv(sockfd,type,sizeof(uint8_t),MSG_WAITALL);
    if(recved==0){tlv->error_type=TLV_E_CONNECT_IS_OVER;return -1;}
    if(recved!=sizeof(uint8_t)){tlv->error_type=TLV_E_RECV_TYPE;return -1;}

    recved = recv(sockfd,length,sizeof(uint32_t),MSG_WAITALL);
    if(recved==0){tlv->error_type=TLV_E_CONNECT_IS_OVER;return -1;}
    if(recved!=sizeof(uint32_t)){tlv->error_type=TLV_E_RECV_LENGTH;return -1;}
    *length=ntohl(*length);

    recved = recv(sockfd,tlv->buffer,*length,MSG_WAITALL);
    if(recved==0){tlv->error_type=TLV_E_CONNECT_IS_OVER;return -1;}
    if(recved!=*length){tlv->error_type=TLV_E_RECV_VALUE;return -1;}

    return 0;
}

int tlv_request(tlv_t *tlv,int sockfd,uint8_t type,uint32_t length,const uint8_t *value,callback_ptr_t callback,void *args){

    if(tlv_send(tlv,sockfd,type,length,value)==-1){return -1;}

    if(tlv_recv(tlv,sockfd,&type,&length)==-1){return -1;}

    if(callback&&(long)callback(tlv,sockfd,type,length,tlv->buffer,args)==-1){
        return -1;
    }

    return 0;
}

int tlv_response(tlv_t *tlv,int sockfd,uint32_t length,const uint8_t *value){

    if(tlv_send(tlv,sockfd,RESPONSE,length,value)==-1){return -1;}

    return 0;
}

int tlv_listen(tlv_t *tlv,int sockfd,callback_ptr_t callback,void *args){
    uint8_t type;
    uint32_t length;
    
    if(tlv_recv(tlv,sockfd,&type,&length)==-1){return -1;}

    if(callback&&(long)callback(tlv,sockfd,type,length,tlv->buffer,args)==-1){
        return -1;
    }

    return 0;
}

const char *tlv_error_str(tlv_t *tlv){
    switch(tlv->error_type){
        case TLV_E_VALUE_TO_LARGE:
            return "VALUE IS TOO LARGE.";
        case TLV_E_SEND_FAIL:
            return "SEND FAIL";
        case TLV_E_CONNECT_IS_OVER:
            return "CONNECT IS OVER";
        case TLV_E_RECV_TYPE:
            return "RECV TYPE ERROR";
        case TLV_E_RECV_LENGTH:
            return "RECV LENGTH ERROR";
        case TLV_E_RECV_VALUE:
            return "RECV VALUE ERROR";
        case TLV_E_CALLBACK:
            return "CALLBACK OCCUR ERROR";
        default:
            break;
    }
    return "";
}
